//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-05-15     ZhangYihua   first version
//
// Description  : 
//################################################################################

module sfifo_ctrl #(
parameter           DEPTH                   = 32,       // must DEPTH>=2

// the following parameters are calculated automatically
parameter           ADDR_BW                 = $clog2(DEPTH),    // address range [0:DEPTH-1]
parameter           CNT_BW                  = $clog2(DEPTH + 1) // counter range [0:DEPTH]
) ( 
input                                       rst_n,
input                                       clk,

input                                       fifo_wen,
output                                      fifo_full,
output                                      fifo_afull,         // afull==0 means free entry in FIFO is at least 'DEPTH-afull_th'

input                                       fifo_ren,
output                                      fifo_empty,
output                                      fifo_aempty,        // aempty==0 means data number in FIFO is at least 'aempty_th' 

output              [CNT_BW-1:0]            fifo_cnt,

// RAPL:    read address pipeline means pipeline steps from mem_raddr to SRAM, 0 means no DFF between them
// WAPL:    write address pipeline means pipeline steps from mem_waddr to SRAM, 0 means no DFF between them
// it's required that RAPL must equals to WAPL at any case.
// case 1: RAPL=WAPL=0, normal operation is ok;
// case 2: RAPL=WAPL=1 without prefetch or prefetch mode is 'RDPL_CACHE', normal operation is ok;
// case 3: RAPL=WAPL=1 with prefetch mode 'RDPL_SHIFT' or 'RDPL_STUFF', let DEPTH = theory_estimation + 1, and
//                     take afull as back pressure to avoid SRAM to overflow;
// case others: not recommended;
output                                      mem_wen,
output              [ADDR_BW-1:0]           mem_waddr,
output                                      mem_ren,
output              [ADDR_BW-1:0]           mem_raddr,

input               [ADDR_BW-1:0]           cfg_fifo_afull_th,  // afull_th<fifo_cnt<=DEPTH is afull, range is [0:DEPTH-1]
input               [ADDR_BW-1:0]           cfg_fifo_aempty_th, // 0<=fifo_cnt<aempty_th is aempty, range is [1:min(DEPTH,2^ADDR_BW-1)]
output                                      err_fifo_ilf,       // fifo internal logic fault
output                                      err_fifo_wof,       // fifo write overflow
output                                      err_fifo_ruf        // fifo read underflow
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          DEPTH_E                 = 1<<ADDR_BW;

localparam  [ADDR_BW-1:0]   POS_ONE         = 1;
localparam  [ADDR_BW-1:0]   NEG_ONE         = {ADDR_BW{1'b1}};
localparam  [ADDR_BW-1:0]   ZERO            = {ADDR_BW{1'b0}};
localparam  [ADDR_BW-1:0]   DEPTH_DEC       = DEPTH - 1;

wire                                        wen;
wire                                        ren;
wire                [ADDR_BW-1:0]           waddr_c;
wire                [ADDR_BW-1:0]           raddr_c;
wire                [ADDR_BW-1:0]           diff;
reg                 [ADDR_BW-1:0]           diff_inc;
wire                                        waddr_ce;
wire                                        raddr_ce;
reg                 [ADDR_BW-1:0]           waddr;
reg                 [ADDR_BW-1:0]           raddr;
reg                 [ADDR_BW-1:0]           cnt;
wire                [CNT_BW-1:0]            afull_th;
wire                [CNT_BW-1:0]            aempty_th;
wire                                        addr_same;
wire                                        wen_only;
wire                                        ren_only;
reg                                         empty;
reg                                         full;
reg                                         aempty;
reg                                         afull;
wire                [ADDR_BW-1:0]           cfg_afull_th_inc;
wire                [ADDR_BW-1:0]           cfg_aempty_th_dec;

//################################################################################
// main
//################################################################################

assign wen = fifo_wen & (~full);
assign ren = fifo_ren & (~empty);

generate if (DEPTH==DEPTH_E) begin:G_PWR2   // DEPTH == 2^ADDR_BW

    assign waddr_ce = wen;
    assign raddr_ce = ren;

    assign waddr_c = waddr + 1'd1;
    assign raddr_c = raddr + 1'd1;

    assign diff = waddr - raddr;
    always@(*) begin
        case ({ren, wen})
            2'b01   : diff_inc = POS_ONE;   // + 1
            2'b10   : diff_inc = NEG_ONE;   // +(-1)
            default : diff_inc = ZERO;      // + 0
        endcase
    end
    
    assign fifo_cnt  = {full, cnt};

    assign afull_th  = {1'b0, cfg_fifo_afull_th};
    assign aempty_th = {1'b0, cfg_fifo_aempty_th};

end else begin:G_NPWR2   // DEPTH < 2^ADDR_BW

    localparam  [ADDR_BW-1:0]   DEPTH_INC       = DEPTH + 1;
    localparam  [ADDR_BW-1:0]   DEPTH_V         = DEPTH;
    wire                                        over;
    wire                                        ofst;

    assign waddr_ce = (waddr>=DEPTH) ? 1'b1 : wen;
    assign raddr_ce = (raddr>=DEPTH) ? 1'b1 : ren;

    assign waddr_c = (waddr>=DEPTH_DEC) ? ZERO : (waddr + 1'd1);
    assign raddr_c = (raddr>=DEPTH_DEC) ? ZERO : (raddr + 1'd1);
    
    assign diff =  waddr-raddr;
    assign over = (waddr<raddr) ? 1'b1 : 1'b0;
    assign ofst = over | full;
    always@(*) begin
        case ({ofst, ren, wen})
            3'b000,
            3'b011  : diff_inc = ZERO;      // + 0
            3'b001  : diff_inc = POS_ONE;   // + 1
            3'b010  : diff_inc = NEG_ONE;   // +(-1)

            3'b101  : diff_inc = DEPTH_INC; // +(DEPTH+1)
            3'b110  : diff_inc = DEPTH_DEC; // +(DEPTH-1)
            default : diff_inc = DEPTH_V;   // + DEPTH
        endcase
    end

    assign fifo_cnt  = cnt;

    assign afull_th  = cfg_fifo_afull_th;
    assign aempty_th = cfg_fifo_aempty_th;

end endgenerate

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        waddr <=`U_DLY {ADDR_BW{1'b0}};
    end else begin
        if (waddr_ce==1'b1)
            waddr <=`U_DLY waddr_c;
        else
            ;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        raddr <=`U_DLY {ADDR_BW{1'b0}};
    end else begin
        if (raddr_ce==1'b1)
            raddr <=`U_DLY raddr_c;
        else
            ;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt <=`U_DLY {ADDR_BW{1'b0}};
    end else begin
        cnt <=`U_DLY diff + diff_inc;
    end
end

assign addr_same = (waddr==raddr) ? 1'b1 : 1'b0; 

assign wen_only = wen & (~ren);
assign ren_only = ren & (~wen);
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        empty <=`U_DLY 1'b1;
    end else begin
        if ((ren_only==1'b1) && (cnt==POS_ONE))
            empty <=`U_DLY 1'b1;
        else if (((wen==1'b1) && (empty==1'b1)) || (addr_same!=1'b1))
            empty <=`U_DLY 1'b0;
        else
            empty <=`U_DLY empty;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        full <=`U_DLY 1'b0;
    end else begin
        if ((wen_only==1'b1) && (cnt==DEPTH_DEC))
            full <=`U_DLY 1'b1;
        else if (((ren==1'b1) && (full==1'b1)) || (addr_same!=1'b1))
            full <=`U_DLY 1'b0;
        else
            full <=`U_DLY ~empty;
    end
end

assign cfg_afull_th_inc  = cfg_fifo_afull_th + 1'b1;
assign cfg_aempty_th_dec = cfg_fifo_aempty_th - 1'b1;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        afull <=`U_DLY 1'b0;
    end else begin
        if ((wen_only==1'b1) && (cnt==cfg_fifo_afull_th))             // fifo_cnt!=DEPTH
            afull <=`U_DLY 1'b1;
        else if ((ren_only==1'b1) && (cnt==cfg_afull_th_inc))    // fifo_cnt!=0
            afull <=`U_DLY 1'b0;
        else if (fifo_cnt>afull_th)
            afull <=`U_DLY 1'b1;
        else
            afull <=`U_DLY 1'b0;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        aempty <=`U_DLY 1'b1;
    end else begin
        if ((ren_only==1'b1) && (cnt==cfg_fifo_aempty_th))    // fifo_cnt!=0
            aempty <=`U_DLY 1'b1;
        else if ((wen_only==1'b1) && (cnt==cfg_aempty_th_dec))   // fifo_cnt!=DEPTH
            aempty <=`U_DLY 1'b0;
        else if (fifo_cnt<aempty_th) 
            aempty <=`U_DLY 1'b1;
        else
            aempty <=`U_DLY 1'b0;
    end
end

assign fifo_full    = full;
assign fifo_empty   = empty;
assign fifo_afull   = afull;
assign fifo_aempty  = aempty;

assign mem_wen      = wen;
assign mem_waddr    = waddr;
assign mem_ren      = ren;
assign mem_raddr    = raddr;

assign err_fifo_wof = fifo_wen & full;
assign err_fifo_ruf = fifo_ren & empty;
assign err_fifo_ilf = (  addr_same  & (~(full ^ empty))) | 
                      ((~addr_same) &   (full | empty) );

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

reg     OVERFLOW_CHKEN;
reg     UNDERFLOW_CHKEN;

a_overflow: assert property (@(posedge clk) disable iff (!rst_n)
    ((OVERFLOW_CHKEN!==1'b0)&fifo_wen |-> (~full))
) else begin
    $error("fifo is overflow.");
end

a_underflow: assert property (@(posedge clk) disable iff (!rst_n)
    ((UNDERFLOW_CHKEN!==1'b0)&fifo_ren |-> (~empty))
) else begin
    $error("fifo is underflow.");
end

// generally define CBB_DEBUG_ON only when debugging CBB
`ifdef CBB_DEBUG_ON

reg     [CNT_BW-1:0]    sva_cnt;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        sva_cnt <=`U_DLY {CNT_BW{1'b0}};
    end else begin
        case ({ren, wen})
            2'b01 : sva_cnt <=`U_DLY sva_cnt + 1'd1;
            2'b10 : sva_cnt <=`U_DLY sva_cnt - 1'd1;
        endcase
    end
end

a_fifo_cnt: assert property (@(posedge clk) disable iff (!rst_n)
    (sva_cnt==fifo_cnt)
) else begin
    $error("fifo_cnt is wrong.");
    $stop;
end

a_fifo_aempty: assert property (@(posedge clk) disable iff (!rst_n)
    ((fifo_cnt>=aempty_th) ^ aempty)
) else begin
    $error("fifo aempty is wrong.");
    $stop;
end

a_fifo_afull: assert property (@(posedge clk) disable iff (!rst_n)
    ((fifo_cnt<=afull_th) ^ afull)
) else begin
    $error("fifo afull is wrong.");
    $stop;
end
a_fifo_ilf: assert property (@(posedge clk) disable iff (!rst_n)
    (~err_fifo_ilf)
) else begin
    $error("fifo internal logic fault.");
    $stop;
end

`endif

// synopsys translate_on
`endif

endmodule
